﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Xml;

namespace Conusic
{
    public static class LastFm
    {
        #region Hints

        #region Regex Define

        /* created by regulator. See lastfm.express in doc folder  */
        const string Expression = @"""artist"":\s?""(?<artist>[^""]*)""[^}]*?  
(""image"":\s?""(?<img>[^""]*)""[^}]*?)?
""restype"":\s?(?<restype>6) 

|
""album"":\s?""(?<album>[^""]*)""[^}]*? 
""artist"":\s?""(?<artist>[^""]*)""[^}]*?  
(""image"":\s?""(?<img>[^""]*)""[^}]*?)?
""restype"":\s?(?<restype>8)

|
""artist"":\s?""(?<artist>[^""]*)""[^}]*?  
""duration"":\s?(?<duration>[0-9]*)[^}]*? 
(""image"":\s?""(?<img>[^""]*)""[^}]*?)?
""restype"":\s?(?<restype>9)[^}]*? 
""track"":\s?""(?<track>[^""]*)"" 

|
""restype"":\s?(?<restype>32)[^}]*?
""tag"":\s?""(?<tag>[^""]*)"" 
";

        private static readonly Regex regex = new Regex(Expression,
                                                        RegexOptions.CultureInvariant
                                                        | RegexOptions.IgnorePatternWhitespace
                                                        | RegexOptions.Compiled
                                                        | RegexOptions.Singleline
            );

        #endregion

        #region Hint Class

        private const int HintMaxLength = 128;

        public enum HintType
        {
            Artist = 6,
            Album = 8,
            Tag = 32,
            Track = 9
        }


        public class Hint
        {
            public HintType HintCategory { get; private set; }
            public string Image { get; private set; }
            public string Name { get; private set; }
            public string Param { get; private set; }
            public int Duration { get; set; }

            public string DurationMS
            {
                get { return String.Format("{0}:{1:D2}", (int)Duration / 60, Duration % 60); }
            }

            public Hint(HintType type, string image, string name)
            {
                this.HintCategory = type;
                this.Image = image;
                this.Name = name;
            }

            public Hint(HintType type, string image, string name, string param)
                : this(type, image, name)
            {
                this.Param = param;
            }

            public Hint(HintType type, string tag)
            {
                this.HintCategory = type;
                this.Name = tag;
            }

            public Hint(HintType type, string image, string name, string param, int duration)
                : this(type, image, name, param)
            {
                Duration = duration;
            }

            public override string ToString()
            {
                switch (HintCategory)
                {
                    case HintType.Artist:
                        return String.Format("{0}", Name);
                    case HintType.Album:
                        return String.Format("{0}  {1}", Name, Param);
                    case HintType.Tag:
                        return String.Format("{0}", Name);
                    case HintType.Track:
                        return String.Format("{0} {1}", Name, Param);
                    default:
                        throw new ArgumentOutOfRangeException();
                }
            }

            public string Json()
            {
                string hintType;
                switch (HintCategory)
                {
                    case HintType.Artist:
                        hintType = "artist";
                        break;
                    case HintType.Album:
                        hintType = "album";
                        break;
                    case HintType.Tag:
                        hintType = "tag";
                        break;
                    case HintType.Track:
                        hintType = "track";
                        break;
                    default:
                        return "{}";
                }
                // {"type" : "type" , "name" : "Name", "param":"Param" , "duration":"duration", "image" : "image"}
                return String.Format("{{\"type\" : \"{0}\", \"name\" : \"{1}\", \"param\":\"{2}\", \"duration\":\"{3}\",\"image\":\"{4}\"}}",
                    hintType,
                    Name,
                    Param,
                    Duration,
                    Image);
            }



            public static explicit operator AudioRecord(Hint hint)
            {
                var temp = new AudioRecord(hint.Name, hint.Param);
                temp.Duration = hint.Duration;
                return temp;
            }
        }

        #endregion

        /// <summary>
        /// Request hints from Last.Fm site. Not recommened to use more 1 times per second. 
        /// </summary>
        /// <param name="query">Text to autocomplete.</param>
        /// <returns>List with suggestion.</returns>
        public static List<Hint> RequestHints(string query)
        {
            if (String.IsNullOrEmpty(query)) throw new ArgumentNullException("query");
            if (query.Length > HintMaxLength) throw new ArgumentException("query");

            var page = SimpleHttp.RequestPage(
                String.Format(@"http://last.fm/search/autocomplete?q={0}&force=1",
                              HttpUtility.UrlEncode(query))
                );

            var match = regex.Match(page);

            if (!match.Success)
                return null;

            List<Hint> result = new List<Hint>();
            while (match.Success)
            {
                var restype = (HintType)int.Parse(match.Groups["restype"].ToString());
                string artist = match.Groups["artist"].ToString();
                string album = match.Groups["album"].ToString();
                string track = match.Groups["track"].ToString();
                string tag = match.Groups["tag"].ToString();
                var image = match.Groups["image"].ToString();
                int duration;
                int.TryParse(match.Groups["duration"].ToString(), out duration);

                switch (restype)
                {
                    case HintType.Artist: // artist
                        result.Add(new Hint(HintType.Artist, image, artist));
                        break;
                    case HintType.Album: // album
                        result.Add(new Hint(HintType.Album, image, artist, album));
                        break;
                    case HintType.Track: // track
                        result.Add(new Hint(HintType.Track, image, artist, track, duration));
                        break;
                    case HintType.Tag: // tag
                        result.Add(new Hint(HintType.Tag, tag));
                        break;
                }
                match = match.NextMatch();
            }
            return result;
        }

        public static string HintListToJson(IEnumerable<Hint> list)
        {
            if (list == null) return "[]";
            var result = new StringBuilder();
            result.AppendLine("[");
            foreach (var hint in list)
            {
                result.Append(String.Format("{0},", hint.Json()));
            }
            result.Remove(result.Length - 1 - Environment.NewLine.Length, 1);
            result.AppendLine("]");
            return result.ToString();
        }

        #endregion

        #region API

        private const string ApiKey = "b25b959554ed76058ac220b7b2e0a026";
        private const string ArtistGetFavoriteSongs = @"http://ws.audioscrobbler.com/2.0/?method=artist.gettoptracks&artist={1}&api_key={0}";
        private const string AlbumGetInfoUrl = @"http://ws.audioscrobbler.com/2.0/?method=album.getinfo&api_key={0}&artist={1}&album={2}";
        private const string TrackGetsimilarUrl = "http://ws.audioscrobbler.com/2.0/?method=track.getsimilar&artist={1}&track={2}&api_key={0}";

        public static List<string> ArtistGetTopTracks(string artist)
        {
            if (String.IsNullOrEmpty(artist) || artist.Length > 32) throw new ArgumentException("artist");

            var result = new List<string>();
            var page = SimpleHttp.RequestPage(String.Format(ArtistGetFavoriteSongs, ApiKey, HttpUtility.UrlEncode(artist)));
            var memStream = new MemoryStream(Encoding.UTF8.GetBytes(page));
            memStream.Position = 0;

            XmlDocument document = new XmlDocument();
            document.Load(memStream);

            foreach (XmlElement element in document.GetElementsByTagName("track"))
            {
                var xmlElement = element["name"];
                if (xmlElement != null) result.Add(xmlElement.InnerText);
            }

            return result;
        }

        public static List<AudioRecord> AlbumGetInfo(string artist, string album)
        {
            if (String.IsNullOrEmpty(artist) || artist.Length > 32) throw new ArgumentException("artist");
            if (String.IsNullOrEmpty(album) || album.Length > 32) throw new ArgumentException("album");

            var result = new List<AudioRecord>();
            var page = SimpleHttp.RequestPage(String.Format(AlbumGetInfoUrl,
                                                            ApiKey,
                                                            HttpUtility.UrlEncode(artist),
                                                            HttpUtility.UrlEncode(album)));

            var memStream = new MemoryStream(Encoding.UTF8.GetBytes(page));
            memStream.Position = 0;

            XmlDocument document = new XmlDocument();
            document.Load(memStream);

            foreach (XmlElement element in document.GetElementsByTagName("track"))
            {
                var xmlElement = element["name"];
                if (xmlElement != null)
                {
                    result.Add(new AudioRecord(artist, xmlElement.InnerText));
                }
            }

            return result;
        }

        public static List<AudioRecord> TrackGetSimular(string artist, string track)
        {
            if (String.IsNullOrEmpty(artist) || artist.Length > 32) throw new ArgumentException("artist");
            if (String.IsNullOrEmpty(track) || track.Length > 32) throw new ArgumentException("track");

            var result = new List<AudioRecord>();
            var page = SimpleHttp.RequestPage(String.Format(TrackGetsimilarUrl,
                                                            ApiKey,
                                                            HttpUtility.UrlEncode(artist),
                                                            HttpUtility.UrlEncode(track)));

            var memStream = new MemoryStream(Encoding.UTF8.GetBytes(page));
            memStream.Position = 0;

            XmlDocument document = new XmlDocument();
            document.Load(memStream);

            foreach (XmlElement element in document.GetElementsByTagName("track"))
            {
                var xmlName = element["name"];
                var xmlArtistNode = element["artist"];
                if (xmlName == null || xmlArtistNode == null)
                    continue;
                var xmlArtistName = xmlArtistNode["name"];
                if (xmlArtistName == null)
                    continue;
                result.Add(new AudioRecord(xmlArtistName.InnerText, xmlName.InnerText));
            }
            return result;
        }


        #endregion
    }
}
